本文为看雪论坛精华文章
看雪论坛作者ID:windy_ll
什么是栈溢出?栈溢出就是缓冲区溢出的一种。由于缓冲区溢出而使得有用的存储单元被改写,往往会引发不可预料的后果。程序在运行过程中,为了临时存取数据的需要,一般都要分配一些内存空间,通常称这些空间为缓冲区。如果向缓冲区中写入超过其本身长度的数据,以致于缓冲区无法容纳,就会造成缓冲区以外的存储单元被改写,这种现象就称为缓冲区溢出。缓冲区长度一般与用户自己定义的缓冲变量的类型有关。(PS:摘自百度百科)简单来说,就是程序没有检查用户输入的数据长度,导致攻击者覆盖栈上不是程序希望写入的地方,比如说返回地址。(PS:本人是这么理解的,如有问题,还请斧正)在x86中,对于调用一个函数,栈的变化如下:首先,将被调用的函数的参数从右到左依次压入栈中,然后将被调用的函数的返回地址压入栈中,然后跳转到被调用函数的地址去,在被调用的函数中,首先将ebp(这时的ebp是调用者的ebp)压入栈中,最后,将此时的栈顶esp赋值给ebp寄存器(此时的ebp便是被调用函数的栈底了)。接着利用gdb调试验证栈的情况如上图所示,首先在test函数处打下断点,然后start命令将程序运行到main函数开头处,查看一下此时的ebp寄存器的值和call test指令后下一条指令的地址,这里可以看到此时ebp寄存器的值为0xffffd1d8,call test指令后的下一条指令地址为0x565561f2,如下图所示:然后运行r命令,进入test函数内部,使用x命令查看此时的栈情况,可以发现栈的情况如上图示意图一样,如下图所示:在test函数内部,有个ver字符数组,在上面的源码中,我们对其进行了手动赋值,假如该数组通过strcpy等函数完成赋值,并且赋值的字符串由用户输入,那么在用户输入的字符串超过12个字节大小之后,ver数组就会接着往高地址增长,在这其中,可以覆盖掉test函数的返回地址、main函数的ebp等等,这就是栈溢出漏洞。下面来看一个具体的程序实例,有漏洞的程序源码如下所示:从源码中可以看到,程序对用户的输入无限制,并且strcpy函数拷贝也无限制,就造成了栈溢出漏洞,下面使用gdb确定偏移地址和getshell函数的首地址,首先断点打在call strcpy的前一行,查看此时的buf数组的地址,也就是eax寄存器的值,再看ebp寄存器的值,发现其两者相距0x10个字节,加上ebp 4个字节,也就是0x14个字节即可到达返回地址,再利用命令disassemble getshell查看getshell函数的首地址,如下图所示:有了偏移量和getshell函数地址就可以写exp了,如下图所示:
二
ret2shellcode
ret2shellcode就是直接部署一段shellcode到栈上或者内存其他位置,当然,要使用的前提是,部署的shellcode所在的位置要具有执行权限,要关闭地址随机化,下面以一道ctf题为例,题目来自ctfhub,首先下载好题目,丢到ida里面反编译一下,如下图所示:可以看到,read函数允许输入的大小为0x400,远大于buf数组的长度,这就造成了栈溢出,并且距离ebp偏移距离为0x10。利用checksec检查一下程序,啥保护都没开,利用file查看一下,是64位的,如下图所示:有了偏移量,并且题目输出给出了buf数组的地址,那么就可以写exp了,首先外面用socat将程序发布到某个端口上去,如下图所示:
ROP的全称是Return Oriented Programming,简单来说就是修改返回地址,指向内存中的指令片段,也就是gadget,通过ret指令将程序的控制器拿在手里。例如一个存在栈溢出的程序,再将返回地址覆盖为pop eax;ret指令地址后,会将返回地址后的4个字节弹到eax寄存器中,然后esp+4,之后又将栈上4个字节弹到eip寄存器中去,而攻击者要做的就是给栈上返回地址后面覆盖要赋给eax的值,下一条gadget的地址,通过这种方式就组合成了一条rop攻击链,栈情况如下图所示(PS:下面图片来自https://zhuanlan.zhihu.com/p/25892385文章中):下面以一道ctf题为例(PS:题目来自ctfwiki),首先下载好题目,然后查看一下常规信息,可以发现为x86架构,开启了NX,也就是说无法在栈上执行shellcode,如下图所示:丢到IDA里面去反编译一下,可以发现是gets函数引起的栈溢出漏洞,并且没有直接可以getshell的函数,如下图所示:那么可以考虑利用rop来getshell,这是一个32为的程序,我们可以通过系统调用来获取shell,常规的执行命令函数的系统调用汇编代码如下图所示:mov eax, 0xb
mov ebx, "/bin/sh"
mov ecx, 0
mov edx, 0
int 80
要给eax赋值,那么我们寻找pop eax;ret之类的代码片段,之所以要有ret指令,是为了要把程序的控制权拿在手中,给其他寄存器赋值也类似,接下来利用ROPgadget工具来寻找相应的代码片段,如下图所示:在这里,我们在寻找控制ecx寄存器的代码片段中,发现可以同时控制ebx、ecx以及edx的一条指令,使用它即可(在图中地址为0x806eb90),接下来,我们需要/bin/sh字符串的地址,打开IDA,键入shift+F12,发现了/bin/sh字符串的地址,如下图所示:当然,我们还需要最重要的偏移量数据,使用gdb调试程序,将断点打在调用gets函数处,然后r,查看当前eax与ebp的距离为0x68(PS:此时eax存放着v4数组的首地址),如下图所示:所有我们编写exp的数据都有了,接下来使用socat将程序发布到某个端口上去,然后执行exp即可,如下图所示:
ret2libc从名字上来看,就是通过覆盖返回地址为libc库中的函数来getshell的一种技术,通常来说,我们会选择system等函数来getshell,但是一般无法获取到这些函数在内存中的绝对地址的,这就需要通过got和plt表泄露以经加载的函数在内存中的地址然后减去其偏移地址,从而拿到libc库的基地址,然后加上system等函数的偏移地址,从而得到system等函数在内存中的地址。plt表和got表是保存程序动态链接的函数地址,程序通过查询plt表获取函数在got表中保存的位置,plt表就相当于一个索引数组,指向got表,程序获取到plt表中相关位置之后,然后查询got表获取到函数地址,之后跳转到该地址去。下面以一道ctf题为例,题目来自ctfwiki,如下所示:可以发现,程序是32位的,并且开启了堆栈不可执行保护,也就是说不可以将shellcode写入栈上执行。再将程序拖进IDA里面看一下,如下图所示:可以看到,溢出点在gets函数,下面用gdb调试一下寻找到偏移量,首先断点下载gets函数,然后查看此时eax与ebp的距离,如下图所示:可以看到,此时eax距离ebp一共为108个字节,再加上这是32位程序,ebp本身占4个字节,也就是说,填充108 + 4 = 112字节后,便是返回地址,找到返回地址后,便是找到system函数地址。这里通过访问got表来拿到一个已经运行过的函数在内存中的地址,在linux中,如果一个函数被调用运行过,那么它的真实地址就会被写进got表中,我们可以通过打印函数将其打印出来,获取其地址,之后通过该地址的后三位(PS:之所以找后三位,是因为即使开启了aslr也不会影响低12位的地址)来确定libc的版本(PS:可以通过在线网站https://libc.blukat.me/来查询),从而获取该版本的libc库中函数的偏移地址,最后泄露地址 - 偏移地址即可得到libc的基地址。然后libc基地址 + 该版本libc库中system偏移地址即可得到system函数在内存中的地址,同理,/bin/sh字符串也是一样的,此处选用的泄露函数地址的函数为puts函数,将程序利用socat发布后,最后exp结果如下图所示(PS:程序最好运行在ubuntu上,经过实际测试,kali上失败,下同):
格式化字符串漏洞,个人理解就是格式字符串参数与其余参数的个数不匹配造成的,网上将原理的文章一大堆,这里就不在重复了。对于格式化字符串漏洞,可以做到读取任意地址的值,也可以往任意地址写入任意值。
对于利用格式化字符串漏洞读取任意地址的值,首先需要确定偏移量,此处的偏移量不是值上面栈溢出的偏移量,而是格式化字符串函数参数的地址相对于格式化字符串参数的偏移量,确定偏移量可以利用形如AAAA%n$x(PS:里面的n就是偏移量)的格式化字符串参数来确定,或者利用AAAA%x%x%x%x%x%x%x%x...(PS:这里也可以使用%p来,但为了防止读到不可读的地址导致程序崩溃,还是推荐使用%x来读取)这种形式来确定。确定的偏移量之后,即可通过addr%n$x来读取任意地址的值(PS:这里的addr指要读取的地址,n为偏移量,当然addr也可以写在后面,把n加1即可,因为%n$x是第一个参数,addr自然是第二个参数,所以n要加上1)对于利用格式化字符串漏洞往任意地址写入值,也是需要先确定偏移量,方法和上面一样,写主要利用%n,%n作用为将前面所写字节数写入指定地址,我们可以利用形如addr%kc%n$n这种形式写入,其中addr为要写入的地址,k为要写入的大小(PS:这里需要减去addr所占用的字节数),n为偏移量,$n表示写入四个字节,当然,也可以使用$hn写入双字节,可以使用$hhn写入一个字节,当然,确定好偏移量之后,最简单的方法是使用pwntools提供的函数即可。下面以一道ctf题举例,题目来自ctfwik,首先下载好题目,解压后,丢到kali里面去,看一下常规信息,可以发现,该程序为32位,开了NX等,如下图所示:丢到IDA中反编译一下,可以发现程序实现了类似ftp的功能,如下图所示:首先程序调用了ask_usename和ask_password两个函数获取一个密码,密码就是将sysbdmin字符串加一,如下图所示:然后程序获取命令,命令有get、put、dir三个命令,获取命令之后,便执行相应的功能,首先来看一下put对应的功能函数,该函数首先要求用户输入一个字符串作为文件名,然后要求用户再输入一个字符串作为文件内容,该函数没什么漏洞,如下图所示:接下来再看一下dir对应的功能函数,该函数作用就是将所有文件名打印出来,也没有什么漏洞,如下图所示:最后来看一下get对应的功能函数,该函数首先要求用户输入文件名,然后将文件名对应的内容拷贝到一个数组中去,最后直接将该数组作为参数传入到printf函数中去,典型的格式化字符串漏洞,如下图所示:通过以上的代码分析,解决该ctf的思路已经很明显了,我们可以首先利用put建立一个文件,将payload写入到该文件中去,之后调用get指令读取该文件,触发格式化字符串漏洞。首先我们先确定偏移量,这里使用BBBB%x....来确定,如下图所示:我们可以通过上图发现,偏移量为7,那么怎么getshell喃,我们可以通过修改got表来实现,将dir指令对应的功能函数中的puts函数修改为system函数的地址,这样调用dir指令后,里面的puts函数实际上会指向system函数,我们通过提前新建一个名为/bon/sh;的文件,即可解决参数的问题,这里怎么写前面ret2libc已经讲了,不在重复,最后使用socat发布程序,指向exp即可,如下图所示:
https://github.com/windy-purple/pwn_study_summary参考链接:
https://www.cnblogs.com/ichunqiu/p/11122229.html
https://www.cnblogs.com/ichunqiu/p/11156155.htmlhttps://www.cnblogs.com/ichunqiu/p/11162515.html
http://drops.xmd5.com/static/drops/tips-4225.html
https://blog.csdn.net/xiaoi123/article/details/80899155
https://bbs.pediy.com/thread-230148.htm
https://sploitfun.wordpress.com/2015/
https://www.jianshu.com/p/187b810e78d2
https://zhuanlan.zhihu.com/p/25816426
https://www.cnblogs.com/Donoy/p/5690402.html
http://shell-storm.org/shellcode/
https://bbs.pediy.com/thread-259723.htm
https://zhuanlan.zhihu.com/p/25892385
http://events.jianshu.io/p/9214e84139eb
https://www.cnblogs.com/wulitaotao/p/13909451.html
https://www.cnblogs.com/hktk1643/p/15218090.html
https://zhuanlan.zhihu.com/p/367387964
https://blog.csdn.net/xiaoi123/article/details/80985646
https://ctf-wiki.org/pwn/windows/readme/
https://libc.blukat.me/
https://blog.csdn.net/qq_41918771/article/details/90665950
https://bbs.pediy.com/thread-253638.htm
https://www.anquanke.com/post/id/83835
https://bbs.pediy.com/thread-254869.htm
https://bbs.pediy.com/thread-262816.htm
看雪ID:windy_ll
https://bbs.pediy.com/user-home-851220.htm
*本文由看雪论坛 windy_ll 原创,转载请注明来自看雪社区